package apibasej.db.server.exec;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.logging.Level;

import apibasej.basic.config.CommonProperties;
import apibasej.basic.config.ConfigManager;
import apibasej.db.server.ConstantsServerDB;
import apibasej.db.to.StatementParam;
import apibasej.db.to.StatementValues;
import apibasej.db.transaction.UtilDBException;

public class UtilJDBC implements ConstantsServerDB, CommonProperties{


	
	// todos index devem estar na lista de params ...
	public static PreparedStatement createPreparedStatement(String sql, ArrayList<StatementParam<?>> params, Connection c) throws SQLException{
		PreparedStatement ps = c.prepareStatement(sql);
		int index = 0;
		for(StatementParam<?> sp : params){
			if(sp.getValue()==null) {
				if(sp.getType()==null){
					ps.setNull(++index,Types.NULL);
				}else{
					ps.setNull(++index,sp.getType());
				}
			}else if(sp.getType()!=null && sp.getType()!=Types.NULL) { // 
				ps.setObject(++index,sp.getValue(),sp.getType());
			}else{
				ps.setObject(++index,sp.getValue());
			}
		}
		return ps;
	}
	
	
	
	// não fecha a conexão !!!!
	public static int executeUpdate(StatementValues sv, Connection c, ConfigManager conf) throws SQLException, UtilDBException{
		
		if(conf==null){
			throw new NullPointerException("ConfigManager cannot be null!");
		}
		
		PreparedStatement p = createPreparedStatement(sv.getSql(), sv.getParams(), c);
		//ps.setQueryTimeout(getQueryTimeoutSeconds());// não é implementado em alguns drivers !!!
		//conf.getProp(CommonProperties.LOGGER).finest("StatementValues: "+sv.getSqlInfo());
		
		if(conf.getProp(DEBUG_SQL)){
			conf.logDef(Level.FINE,"StatementValues: "+sv.getSqlInfo(),null);
		}
		
		// se for autocommit deve configurar uma transação para poder fazer rollback se der erro de validação
		boolean useTransactionHere = c.getAutoCommit();
		// deve verificar se já não estava auto-commit, pois se setar em uma conexão que já tinha transação pode dar erro dependendo do pool
		// EX pool do jboss:  
		//Caused by: java.sql.SQLException: You cannot set autocommit during a managed transaction!
		if(useTransactionHere){
			c.setAutoCommit(false);
		}
		try{
			TriggerActionsDB trigger = conf.getProp(PROP_TRGGER_ACTIONS_DB);
			// !!! valida para não ter perigo de executar ações sem a trigger, se não quiser fazer nada setar a TriggerActionsDBNull
			if(trigger==null){
				throw new UtilDBException("PROP_TRGGER_ACTIONS_DB not setted!");
			}
			
			Object paramsBefore = null;
			if(trigger!=null){
				paramsBefore = trigger.beforeExecuteUpdate(sv, c, conf);
			}
			int afRows = p.executeUpdate();
			
			if(trigger!=null){
				trigger.afterExecuteUpdate(sv, c, conf, afRows, paramsBefore);
			}
			
			if(sv.getQtdAffectedRowsToValidate()!=null && sv.getQtdAffectedRowsToValidate()!=afRows){
				if(useTransactionHere){
					c.rollback();
				}
				throw new UtilDBException("Invalid number of rows affected:"+afRows+". Expected: "+sv.getQtdAffectedRowsToValidate());
			}
			if(sv.getMaxAffectedRowsToValidate()!=null && afRows>sv.getMaxAffectedRowsToValidate()){
				if(useTransactionHere){
					c.rollback();
				}
				throw new UtilDBException("Maximum number of rows affected exceeded:"+afRows+". Expected: "+sv.getMaxAffectedRowsToValidate());
			}
			if(useTransactionHere){
				c.commit();
			}
			return afRows;
		}catch(SQLException e1){
			if(useTransactionHere){
				c.rollback();
			}
			throw e1;
		}catch(UtilDBException e1){
			if(useTransactionHere){
				c.rollback();
			}
			throw e1;			
		}finally{
			// se estava autocommit antes voltar para AutoCommit=true
			if(useTransactionHere){
				c.setAutoCommit(true);
			}
			closeStatement(p, conf);
		}
	}
	
	private static void closeStatement(Statement st, ConfigManager c){
		if(st!=null) {
			try{
				st.close();
			}catch (Throwable t) {
				ConfigManager.logDefAll(Level.WARNING,"Error on close Statement", t);
			};
		}
	}
	
}

